package br.com.covosys.dao.impl;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.EmbeddedId;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import org.springframework.beans.BeanUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import br.com.covosys.dao.DAOException;
import br.com.covosys.dao.GenericDAO;
import br.com.covosys.dao.PropertiesReader;
import br.com.covosys.jpa.annotation.FindLike;
import br.com.covosys.jpa.annotation.FindLikeType;
import br.com.covosys.util.BasicUtils;
import br.com.covosys.util.ReflectUtils;

public abstract class GenericDAOImpl<BEAN extends Serializable> extends PropertiesReader implements GenericDAO<BEAN> {	
	
	private static final long serialVersionUID = 1L;
	
	@PersistenceContext
	private EntityManager entityManager;
	
	protected Class<BEAN> beanClass;
	
	@SuppressWarnings("unchecked")
	public GenericDAOImpl(String fileName) {
		super("/dao/" + fileName);
		try {
			ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
			beanClass = (Class<BEAN>) parameterizedType.getActualTypeArguments()[0];
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	
	@Override
	public BEAN retrieve(BEAN bean) throws DAOException {
		BEAN findedBean = entityManager.find(beanClass, getKey(bean));
		return findedBean;
	}

	@Override
	public void save(BEAN bean) throws DAOException {
		if (isNew(bean)) {
			insert(bean);
		} else {
			update(bean);
		}
		entityManager.flush();
	}

	@Override
	public void delete(BEAN bean) throws DAOException {
		BEAN saved = retrieve(bean);
		entityManager.remove(saved);
		entityManager.flush();
	}

	@Override
	public List<BEAN> list() throws DAOException {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BEAN> criteriaQuery = criteriaBuilder.createQuery(beanClass);
        criteriaQuery.from(beanClass);
        TypedQuery<BEAN> typedQuery = entityManager.createQuery(criteriaQuery);
        return typedQuery.getResultList();
	}
	
	public List<BEAN> executeKey(String key) {
		return executeKey(key, new ArrayList<Object>());
	}
	
	public List<BEAN> executeKey(String key, List<Object> parametros) {
		
		String query = super.getKey(key);
		
		TypedQuery<BEAN> typedQuery = entityManager.createQuery(query, beanClass);
		
		int contador = 0;
		
		for (Object object : parametros) {
			typedQuery.setParameter(contador++, object);
		}
		
		List<BEAN> resultado = typedQuery.getResultList();
		
		return resultado;
	}
	
	//TODO: LIXO
	/**
	 * refazer
	 */
	@Override
	public List<BEAN> list(Map<String, Object> parametros) throws DAOException {

		Map<String, Object> copiaDeParametos = new HashMap<String, Object>(parametros);
		
		StringBuilder jpaQL = new StringBuilder();
		
		String beanClassName = StringUtils.uncapitalize(beanClass.getSimpleName());
		
		jpaQL.append(super.getKey(beanClassName + "." + "select"));
		
		boolean adicinouWhere = false;
	
		Iterator<String> chaves = copiaDeParametos.keySet().iterator();
		
		while (chaves.hasNext()) {
			
			String key = chaves.next();
			
			String jpaKey = beanClassName + "." + key;
			
			Object value = parametros.get(key);
			
			if (value == null || (value instanceof String && "".equals(value))) {
				continue;
			}
			
			if (!adicinouWhere) {
				jpaQL.append(" WHERE ");
				adicinouWhere = true;
			} else {
				jpaQL.append(" AND ");
			}
			
			Field field = ReflectionUtils.findField(beanClass, key);
		
			if (field != null && field.isAnnotationPresent(FindLike.class) && value instanceof String) {
				
				FindLikeType findLikeType = field.getAnnotation(FindLike.class).value();
				
				jpaQL.append(jpaKey + " LIKE ?");
				
				if (findLikeType == FindLikeType.BOTH) {
					value = "%" + ((String) value) + "%";
				} else if (findLikeType == FindLikeType.LEFT) {
					value = "%" + ((String) value);	
				} else if (findLikeType == FindLikeType.RIGHT) {
					value = ((String) value) + "%";
				}
				
				copiaDeParametos.put(key, value);
				
			} else {
				jpaQL.append(jpaKey + " = ?");				
			}	
			
		}
		
		String orderBy = super.getKey(beanClassName + "." + "orderBy");
		
		if (orderBy != null) {
			jpaQL = jpaQL.append(" ORDER BY " + orderBy);
		}
		
		TypedQuery<BEAN> typedQuery = entityManager.createQuery(jpaQL.toString(), beanClass);
		
		int parametro = 1;
		
		for (String key : copiaDeParametos.keySet()) {
			
			Object value = copiaDeParametos.get(key);

			if (value == null || (value instanceof String && "".equals(value))) {
				continue;
			}
			
			typedQuery.setParameter(parametro++, value);
			
		}
		
		return typedQuery.getResultList();
	}
	
	public EntityManager getEntityManager() {
		return entityManager;
	}

	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}
	
	protected void insert(BEAN bean) throws DAOException {
		entityManager.persist(bean);
	}
	
	protected void update(BEAN bean) throws DAOException {
		entityManager.merge(bean);
	}
	
	protected boolean isNew(BEAN bean) throws DAOException {
		return getKey(bean) == null;
	}
	
	protected void updateDataCopy(BEAN persistedBean, BEAN newBean) throws DAOException {
		try {
			BeanUtils.copyProperties(persistedBean, newBean);
		} catch (Exception exception) {
			throw new DAOException(exception);
		}
	}
	
	protected Object getKey(BEAN bean) throws DAOException {
		
		try {
			
			List<Field> fields = ReflectUtils.getField(bean, Id.class);
			
			if (BasicUtils.isEmpty(fields)){	
				fields = ReflectUtils.getField(bean, EmbeddedId.class);
			}
			
			if (!BasicUtils.isEmpty(fields)){
			
				Object result = ReflectUtils.getObjectProprety(bean, fields.get(0).getName());
				
				if (result != null && result instanceof Number && ((Number) result).doubleValue() == 0){		
					result = null;					
				}
				
				return result;
				
			} else {
				throw new DAOException("O bean nao tem chave configurada. " + bean.getClass().getName());
			}
			
		} catch (Exception exception) {			
			throw new DAOException("Nao foi possivel recuperar a chave do objeto.", exception);
		}
	}
}